package controllers

import java.io.File
import java.nio.file.Files
import javax.inject.Inject
import org.apache.commons.io.FileUtils
import play.api.mvc.{AbstractController, ControllerComponents}
import tool._
import utils.Utils
import models.Tables._
import org.joda.time.DateTime
import dao._
import org.apache.commons.lang3.StringUtils
import play.api.libs.json.{JsObject, Json}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import utils.Implicits._
import implicits.Implicits._
import org.zeroturnaround.zip.ZipUtil
import shared.Shared
import shared.pojo.App.{DirData, ExportPdfData, MissionIdData}
import shared.pojo.Mission.MissionInfo
import spdf._
import upickle.default._

/** Created by yz on 2019/1/3
  */
class PredictController @Inject() (
    cc: ControllerComponents,
    formTool: FormTool,
    tool: Tool,
    missionDao: MissionDao,
    extraDataDao: ExtraDataDao,
    implicit val rangeDao: RangeDao,
    trashDao: TrashDao
)(implicit
    val pdfInfoDao: PdfInfoDao
) extends AbstractController(cc) {

  def predictBefore = Action { implicit request =>
    Ok(views.html.user.predict())
  }

  def predict = Action.async { implicit request =>
    val data = formTool.predictForm.bindFromRequest().get
    val userId = Tool.getUserId
    val tmpDir = tool.createTempDirectory("tmpDir")
    val inputFile = new File(tmpDir, "input.txt")
    val headers = List("SampleID", "Age", "AST", "ALT", "PLT")
    Tool.produceRangeFile(userId, tmpDir)
    val lines = headers :: List(
      List(data.sampleId, data.age, data.ast, data.alt, data.plt)
    )
    lines.toTxtFile(inputFile)
    Utils.txt2Xlsx(inputFile, new File(tmpDir, "input.xlsx"))
    FileUtils.copyFileToDirectory(new File(tool.dataDir, "LiveBoost_load.RData"), tmpDir)
    val windowsCommand =
      s"""
         |${tool.rScript} ${Utils.dosPath2Dos(tool.rPath)}/predict_v1.R
       """.stripMargin
    val command = windowsCommand
    val startTime = System.currentTimeMillis()
    val execCommand = Utils.callScript(tmpDir, shBuffer = List(command))
    if (execCommand.isSuccess) {
      val outFile = new File(tmpDir, "out.txt")
      val resultData = tool.getResultData(outFile)
      val score = Shared.calculateScore(resultData)
      val result = Shared.getResult(score)
      val time = new DateTime()
      val row = MissionRow(
        0,
        userId,
        data.sampleId,
        data.name,
        data.age,
        data.ast,
        data.alt,
        data.plt,
        score.toString(),
        result,
        time
      )
      missionDao.insertAndRetunId(row).flatMap { missionId =>
        val extraRow = tool
          .getEmptyExtraDataRow(missionId, userId)
          .copy(sampleId = data.sampleId, name = data.name, checkDate = Utils.dataTime2String(time))
        val trashRow = TrashRow(missionId, userId, true)
        extraDataDao.insertOrUpdate(extraRow).zip(trashDao.insert(trashRow)).map { x =>
          val missionIdDir = tool.getMissionIdDirById(missionId)
          val outFile = new File(tmpDir, "out.txt")
          FileUtils.copyFileToDirectory(outFile, missionIdDir)
          tool.deleteDirectory(tmpDir)
          val json = Json.obj("missionId" -> missionId)
          Ok(json)
        }
      }
    } else {
      tool.deleteDirectory(tmpDir)
      Utils.result2Future(Ok(Json.obj("valid" -> "false", "message" -> execCommand.getErrStr)))
    }
  }

  def exportPdf = Action.async { implicit request =>
    val data = formTool.basicInfoForm.bindFromRequest().get
    val pdfData = formTool.pdfDataForm.bindFromRequest().get
    val missionId = formTool.missionIdForm.bindFromRequest().get.missionId
    val tmpDir = tool.createTempDirectory("tmpDir")
    val outPdfFile = new File(tmpDir, "live_boost_out.pdf")
    val pdf = Pdf(
      s"${tool.binDir}/wkhtmltopdf/bin/wkhtmltopdf.exe",
      new PdfConfig {
        orientation := Portrait
        pageSize := "A4"
        pageSize := "Letter"
        marginTop := "0.5in"
        marginBottom := "0in"
        marginLeft := "0.5in"
        marginRight := "0.5in"
      }
    )
    missionDao.selectByMissionId(missionId).flatMap { row =>
      val missionIdDir = tool.getMissionIdDirById(missionId)
      val outFile = new File(missionIdDir, "out.txt")
      val pngFile = new File(missionIdDir, "out.png")
      val svgFile = new File(missionIdDir, "out.svg")
      FileUtils.writeStringToFile(svgFile, pdfData.svgStr)
      Utils.svg2png(svgFile)
      val base64 = Utils.getBase64Str(pngFile)
      val logoFile = new File(tool.dataDir, "logo.png")
      val logoBase64 = logoFile.base64
      val qrBase64 = new File(tool.dataDir, "qr_code.jpg").base64
      val imageInfo = Map("barPlot" -> base64, "logo" -> logoBase64, "qrCode" -> qrBase64)
      val htmlFile = new File(tmpDir, "out.html")
      val str = views.html.user.html(data, row, imageInfo, pdfData).toString()
      FileUtils.writeStringToFile(htmlFile, str)
      pdf.run(views.html.user.html(data, row, imageInfo, pdfData).toString(), outPdfFile)
      val bytes = FileUtils.readFileToByteArray(outPdfFile)
      val extraDataRow = ExtraDataRow(
        row.id,
        row.userId,
        data.sample,
        data.unit,
        data.address,
        data.name,
        data.sex,
        data.office,
        data.doctor,
        data.number,
        data.sampleTime,
        data.submitTime,
        data.sampleType,
        data.sampleStatus,
        data.title,
        data.danger,
        data.reporter,
        data.checker,
        data.checkDate,
        data.reportDate
      )
      extraDataDao.insertOrUpdate(extraDataRow).map { x =>
        tool.deleteDirectory(tmpDir)
        Ok(bytes).as("application/pdf")
      }
    }
  }

  def getDanger(score: BigDecimal) = {
    score match {
      case x if x <= 0.25 => "肝脏代谢指标综合分析良好。肝纤维化、肝硬化的可能性比较低。本结果仅供参考。"
      case x if x > 0.25 && x <= 0.5 => "肝脏代谢指标综合分析出现异常。早期肝纤维化的可能性非常大。本结果仅供参考，肝纤维化、肝硬化程度请结合临床。"
      case x if x > 0.5 && x <= 0.75 => "肝脏代谢指标综合分析出现异常。晚期肝纤维化的可能性非常大。本结果仅供参考，肝纤维化、肝硬化程度请结合临床。"
      case x if x > 0.75 => "肝脏代谢指标综合分析出现异常。肝硬化的可能性非常大。本结果仅供参考，肝纤维化、肝硬化程度请结合临床。"
    }
  }

  def exportById = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[ExportPdfData])
    val userId = Tool.getUserId
    extraDataDao
      .selectByMissionIds(data.missionIds)
      .zip(pdfInfoDao.selectById(userId))
      .zip(missionDao.selectByMissionIds(data.missionIds))
      .flatMap { case ((extraRows, pdfInfo), missionRows) =>
        val tmpDir = tool.createTempDirectory("tmpDir")
        val outZipDir = new File(tmpDir, "live_boost_out").createDirectoryWhenNoExist
        val extraRowMap = extraRows.map(x => (x.id, x)).toMap
        val svgStrMap = data.missionIds.zip(data.svgStrs).toMap
        val extraDataRows = missionRows.map { missionRow =>
          val extraRow = extraRowMap(missionRow.id)
          val basicInfoData = BasicInfoData(
            title = if (extraRow.title.nonBlank) extraRow.title else pdfInfo.title,
            unit = if (extraRow.unit.nonBlank) extraRow.unit else pdfInfo.unit,
            address = if (extraRow.address.nonBlank) extraRow.address else pdfInfo.address,
            reporter = if (extraRow.reporter.nonBlank) extraRow.reporter else pdfInfo.reporter,
            checker = if (extraRow.checker.nonBlank) extraRow.checker else pdfInfo.checker,
            name = extraRow.name,
            sex = extraRow.sex,
            age = missionRow.age,
            office = extraRow.office,
            doctor = extraRow.doctor,
            number = extraRow.number,
            sampleTime = extraRow.sampleTime,
            submitTime = extraRow.submitTime,
            sample = extraRow.sampleId,
            sampleType = extraRow.sampleType,
            sampleStatus = extraRow.sampleStatus,
            checkDate = extraRow.checkDate,
            reportDate =
              if (extraRow.reportDate.nonBlank) extraRow.reportDate
              else
                Utils
                  .dataTime2String(new DateTime()),
            danger = if (extraRow.danger.nonBlank) extraRow.danger else getDanger(missionRow.score.toDouble)
          )
          val pdfData = PdfData(
            score = missionRow.score,
            result = missionRow.result,
            svgStr = svgStrMap(missionRow.id)
          )
          val pdf = Pdf(
            s"${tool.binDir}/wkhtmltopdf/bin/wkhtmltopdf.exe",
            new PdfConfig {
              orientation := Portrait
              pageSize := "A4"
              pageSize := "Letter"
              marginTop := "0.5in"
              marginBottom := "0in"
              marginLeft := "0.5in"
              marginRight := "0.5in"
            }
          )
          val pngFile = new File(tmpDir, s"${missionRow.id}.png")
          val svgFile = new File(tmpDir, s"${missionRow.id}.svg")
          FileUtils.writeStringToFile(svgFile, pdfData.svgStr)
          Utils.svg2png(svgFile)
          val base64 = Utils.getBase64Str(pngFile)
          val logoFile = new File(tool.dataDir, "logo.png")
          val logoBase64 = logoFile.base64
          val qrBase64 = new File(tool.dataDir, "qr_code.jpg").base64
          val imageInfo = Map("barPlot" -> base64, "logo" -> logoBase64, "qrCode" -> qrBase64)
          val outPdfFile = new File(outZipDir, s"${missionRow.sampleId}.pdf")
          pdf.run(views.html.user.html(basicInfoData, missionRow, imageInfo, pdfData).toString(), outPdfFile)
          ExtraDataRow(
            missionRow.id,
            missionRow.userId,
            basicInfoData.sample,
            basicInfoData.unit,
            basicInfoData.address,
            basicInfoData.name,
            basicInfoData.sex,
            basicInfoData.office,
            basicInfoData.doctor,
            basicInfoData.number,
            basicInfoData.sampleTime,
            basicInfoData.submitTime,
            basicInfoData.sampleType,
            basicInfoData.sampleStatus,
            basicInfoData.title,
            basicInfoData.danger,
            basicInfoData.reporter,
            basicInfoData.checker,
            basicInfoData.checkDate,
            basicInfoData.reportDate
          )
        }
        extraDataDao.insertOrUpdates(extraDataRows.toList).map { x =>
          val zipFile = new File(tmpDir, "live_boost_out.zip")
          ZipUtil.pack(outZipDir, zipFile)
          Ok(writeJs(DirData(dir = tmpDir.getAbsolutePath)))
        }
      }
  }

  def exportPdfByDir = Action { implicit request =>
    val data = Tool.transformRq.transform(reader[DirData])
    val tmpDir = new File(data.dir)
    val outPdfFile = new File(tmpDir, "live_boost_out.zip")
    Ok.sendFile(
      outPdfFile,
      onClose = () => {
        Utils.deleteDirectory(tmpDir)
      }
    ).withHeaders(
      CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${outPdfFile.getName}"),
      CONTENT_TYPE -> "application/x-download"
    )
  }

  def batchPredictBefore = Action { implicit request =>
    Ok(views.html.user.batchPredict())
  }

  def downloadExampleFile = Action { implicit request =>
    val data = formTool.fileNameForm.bindFromRequest().get
    val file = new File(Utils.path, s"example/${data.fileName}")
    Ok.sendFile(file)
      .withHeaders(
        CACHE_CONTROL -> "max-age=3600",
        CONTENT_DISPOSITION -> s"attachment; filename=${file.getName}",
        CONTENT_TYPE -> "application/x-download"
      )
  }

  def fileCheck = Action(parse.multipartFormData) { implicit request =>
    val tmpDir = tool.createTempDirectory("tmpDir")
    val dataFile = new File(tmpDir, "data.xlsx")
    val file = request.body.file("file").get
    file.ref.moveTo(dataFile, replace = true)
    val myMessage = tool.fileCheck(dataFile)
    tool.deleteDirectory(tmpDir)
    Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message))
  }

  def batchPredict = Action.async(parse.multipartFormData) { implicit request =>
    val userId = Tool.getUserId
    val tmpDir = tool.createTempDirectory("tmpDir")
    val inputFile = new File(tmpDir, "input.xlsx")
    val file = request.body.file("file").get
    file.ref.moveTo(inputFile, replace = true)
    Tool.produceRangeFile(userId, tmpDir)
    val time = new DateTime()
    FileUtils.copyFileToDirectory(new File(tool.dataDir, "LiveBoost_load.RData"), tmpDir)
    val command =
      s"""
         |${tool.rScript} ${(tool.rPath)}/predict_v1.R
       """.stripMargin
    val execCommand = Utils.callScript(tmpDir, shBuffer = List(command))
    if (execCommand.isSuccess) {
      val outFile = new File(tmpDir, "out.txt")
      val outLines = outFile.lines
      val fileContents = outLines.drop(1).map { line =>
        List(outLines.head, line)
      }
      val missions = fileContents.map { newLines =>
        val resultData = tool.getResultData(newLines)
        val score = Shared.calculateScore(resultData)
        val result = Shared.getResult(score)
        val orinalData = tool.getOrinalData(newLines)
        MissionRow(
          0,
          userId,
          orinalData.sampleId,
          "",
          orinalData.age,
          orinalData.ast,
          orinalData.alt,
          orinalData.plt,
          score.toString(),
          result,
          time
        )
      }
      val f = missionDao.insertAndRetunIds(missions).flatMap { missionIds =>
        val etRows = missionIds.zip(fileContents).map { case (missionId, newLines) =>
          val missionIdDir = tool.getMissionIdDirById(missionId)
          val outFile = new File(missionIdDir, "out.txt")
          newLines.toFile(outFile)
          val extraData = tool.getExtraData(outFile)
          val checkDate =
            if (StringUtils.isEmpty(extraData.checkDate)) Utils.dataTime2String(time) else extraData.checkDate
          val orinalData = tool.getOrinalData(newLines)
          val extraRow = tool
            .getEmptyExtraDataRow(missionId, userId)
            .copy(
              sampleId = orinalData.sampleId,
              name = extraData.name,
              unit = extraData.unit,
              address = extraData.address,
              sex = extraData.sex,
              office = extraData.office,
              doctor = extraData.doctor,
              number = extraData.number,
              sampleTime = extraData.sampleTime,
              submitTime = extraData.submitTime,
              sampleType = extraData.sampleType,
              sampleStatus = extraData.sampleStatus,
              title = extraData.title,
              reporter = extraData.reporter,
              checker = extraData.checker,
              checkDate = checkDate,
              reportDate = extraData.reportDate
            )
          val trashRow = TrashRow(missionId, userId, true)
          (extraRow, trashRow)
        }
        extraDataDao.insertOrUpdates(etRows).map { x =>
          missionIds
        }
      }
      val missionIds = Utils.execFuture(f)
      tool.deleteDirectory(tmpDir)
      missionDao.selectByMissionIds(missionIds).zip(extraDataDao.selectByMissionIds(missionIds)).map {
        case (x, extraRows) =>
          val missionRows = x
          val extraMap = extraRows.map(x => (x.id, x)).toMap
          val t2s = missionRows.map(x => (x, extraMap.getOrElse(x.id, tool.getEmptyExtraDataRow(x.id, x.userId))))
          val array = Utils.getArrayByT2s(t2s)
          Ok(Json.toJson(array))
      }
    } else {
      tool.deleteDirectory(tmpDir)
      Utils.result2Future(Ok(Json.obj("valid" -> "false", "message" -> execCommand.getErrStr)))
    }

  }

  def predictResult = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[MissionIdData])
    missionDao.selectByMissionId(data.missionId).zip(extraDataDao.selectByMissionId(data.missionId)).map {
      case (row, extraRow) =>
        val missionIdDir = tool.getMissionIdDirById(data.missionId)
        val outFile = new File(missionIdDir, "out.txt")
        val resultData = tool.getResultData(outFile)
        val extraDataJson = Utils.getJsonByT(extraRow)
        val missionJson = tool.getMissionJson(row, resultData)
        val resultJson = Utils.getJsonByT(resultData)
        val json = Json.obj(
          "missionId" -> data.missionId,
          "sampleId" -> row.sampleId,
          "name" -> row.name,
          "age" -> row.age,
          "extraData" -> extraDataJson,
          "mission" -> missionJson
        ) ++ resultJson.as[JsObject]
        Ok(json)
    }

  }

}
